<?php

class SelectQueryBuilder implements SQLQueryBuilder {

    /**
     * @var FieldCollection
     */
    protected $fields;

    /**
     * @var Table
     */
    protected $tableName;

    /**
     * @var CriteriaCollection
     */
    protected $criterion;

    /**
     * @var FieldCollection
     */
    protected $groupBy;

    /**
     * @var JoinCollection
     */
    protected $joins;

    /**
     * @var OrderByCollection
     */
    protected $orderBy;

    /**
     * @var string
     */
    protected $limit;

    /**
     * @var CriteriaCollection
     */
    protected $having;

    /**
     * @var boolean
     */
    public $isDistinct;

    /**
     * @param string[] $fields
     * @param string $tableName
     */
    public function __construct($fields, $tableName) {
        $this->fields = new FieldCollection($fields);
        $this->table = new Table($tableName);
        $this->criterion = new CriteriaCollection();
        $this->joins = new JoinCollection();
        $this->orderBy = new OrderByCollection();
        $this->having = new CriteriaCollection();
        $this->groupBy = new FieldCollection();
    }

    /**
     * @return string
     */
    private function getSelectClause() {

        $select = $this->isDistinct ? 'SELECT DISTINCT ' : 'SELECT ';
        $select .= $this->fields->getFieldsSeparatedByComma();
        return $select;
    }

    /**
     * @return string
     */
    private function getFromClause() {
        return ' FROM ' . $this->table->getName();
    }

    /**
     * @return string
     */
    private function getJoinClause() {
        $joinCollectionString = $this->joins->getJoinCollectionString();
       return $joinCollectionString ? ' ' . $joinCollectionString : '';
    }

    /**
     * @return string
     */
    private function getWhereClause() {
        if(is_null($this->criterion) || $this->criterion->getCriteriaCount() == 0) { return ''; }
        $whereClause = ' WHERE ';
        $whereClause .= $this->criterion->getCriteriaString();
        return $whereClause;
    }

    /**
     * @return string
     */
    private function getOrderByClause() {
        if($this->orderBy->getOrderByCount() == 0) { return ''; }
        return ' ORDER BY ' . $this->orderBy->getOrderByCollectionString();
    }

    private function getGroupByClause() {
        if($this->groupBy->getFieldCount() == 0) { return ''; }
        return ' GROUP BY ' . $this->groupBy->getFieldsSeparatedByComma();
    }

    private function getHavingClause() {
        if(is_null($this->having) || $this->having->getCriteriaCount() == 0) { return ''; }
        $havingClause = ' HAVING ';
        $havingClause .= $this->having->getCriteriaString();
        return $havingClause;
    }

    /**
     * @return string
     */
    private function getLimitClause() {
        return $this->limit ? ' LIMIT ' . $this->limit : '';
    }

    /**
     * @return string
     */
    public function getSQLQuery() {
        $sqlString = $this->getSelectClause();
        $sqlString .= $this->getFromClause();
        $sqlString .= $this->getJoinClause();
        $sqlString .= $this->getWhereClause();
        $sqlString .= $this->getOrderByClause();
        $sqlString .= $this->getGroupByClause();
        $sqlString .= $this->getHavingClause();
        $sqlString .= $this->getLimitClause();
        $sqlString .= ';';
        return $sqlString;
    }

    /**
     * @param Criteria $criteria
     */
    public function addCriteria($criteria) {
        $this->criterion->addCriteria($criteria);
    }

    /**
     * @param Criteria $criteria
     */
    public function addOrCriteria($criteria) {
        $this->criterion->addOrCriteria($criteria);
    }

    /**
     * @param string $tableName
     * @param Criteria $criteria
     * @param string $type
     */
    public function joinTable($tableName, $criteria = null, $type = '') {
        $this->joins->createAndAddJoin($this->table->getName(), $tableName, $criteria, $type );
    }

    /**
     * @param string $field
     * @param string $type
     */
    public function orderBy($field, $type = '') {
        $this->orderBy->addOrderByByFieldNameAndType($field, $type);
    }

    /**
     * @param int $startingPoint
     * @param int $numberOfRecordsToDisplay
     */
    public function addLimit($startingPoint, $numberOfRecordsToDisplay) {
        $this->limit = $startingPoint . ',' . $numberOfRecordsToDisplay;
    }

    /**
     * @param string[] $fields
     */
    public function groupByFields($fields) {
        $this->groupBy = new FieldCollection($fields);
    }

    /**
     * @param string $field
     */
    public function groupByField($field) {
        $this->groupBy->addField($field);
    }

    /**
     * @param Criteria $criteria
     */
    public function having($criteria) {
        $this->having->addCriteria($criteria);
    }

    /**
     * @return string
     */
    public function getTableName() {
        return $this->table->getName();
    }

}